using System;
using System.Collections.Generic;
using Godot;

namespace ZFramework
{
    public class Palette<T>(int width, int height, int length)
    {
        private readonly int?[,,] _indices = new int?[width, height, length];
        private readonly List<T> _entries = [];

        public T this[int x, int y, int z]
        {
            get
            {
                if (_indices[x, y, z] is int idx)
                {
                    return _entries[idx];
                }
                return default;
            }

            set
            {
                var idx = _entries.FindIndex(entry => entry.Equals(value));
                if (idx == -1)
                {
                    idx = _entries.Count;
                    _entries.Add(value);
                }
                _indices[x, y, z] = idx;
            }
        }

        public int Width => _indices.GetLength(0);
        public int Height => _indices.GetLength(1);
        public int Length => _indices.GetLength(2);

        public bool ContainsPoint(int x, int y, int z) => x >= 0 && x < Width && y >= 0 && y < Height && z >= 0 && z < Length;
        public bool HasValue(int x, int y, int z) => ContainsPoint(x, y, z) && _indices[x, y, z] != null;
        public bool HasValue(Vector3I localPos) => HasValue(localPos.X, localPos.Y, localPos.Z);

        public void Remove(int x, int y, int z)
        {
            _indices[x, y, z] = null;
        }

        public void Retight()
        {
            var tempIndices = new int?[Width, Height, Length];
            var tempEntries = new List<T>();

            var oldToNew = new Dictionary<int, int>();

            for (var z = 0; z < Length; z++)
            {
                for (var y = 0; y < Height; y++)
                {
                    for (var x = 0; x < Width; x++)
                    {
                        if (_indices[x, y, z] is int oldIndex)
                        {
                            if (oldToNew.TryGetValue(oldIndex, out int index))
                            {
                                tempIndices[x, y, z] = index;
                            }
                            else
                            {
                                var idx = tempEntries.Count;
                                tempEntries.Add(_entries[oldIndex]);
                                oldToNew.Add(oldIndex, idx);
                            }
                        }
                    }
                }
            }

            Array.Copy(tempIndices, _indices, tempIndices.Length);
            _entries.Clear();
            _entries.AddRange(tempEntries);
        }
    }
}